package main

import (
    "errors"
    "encoding/json"
    "io/ioutil"
    "net/http"
    "time"
    "sync"
)

type DeskTopClientInfo struct {
    Node        string `json:"node"`
    VMId        string `json:"vmid"`
    Name        string `json:"name"`
    Protocol    string `json:"protocol"`
    UserName    string `json:"username"`
    LocalIp     string `json:"local_ip"`
    Platform    string `json:"platform"`
    InstanceId  string `json:"instance_id"`
}

func (i *DeskTopClientInfo) getId() string {
    if i.Node == "" || i.VMId == "" {
        return ""
    }
    return i.Node + "/" + i.VMId
}

type DeskTopClient struct {
    Id          string `json:"id"`
    HeartBeat   int64 `json:"heart_beat"`
    UpTime      int64 `json:"uptime"`
    DeskTopClientInfo
}

func newDeskTopClient(info *DeskTopClientInfo) *DeskTopClient {
    dc := &DeskTopClient{}
    dc.Id = info.getId()
    dc.UpTime = time.Now().Unix()
    dc.onHeartBeat(info)
    return dc
}

func (dc *DeskTopClient) updateInfo(info *DeskTopClientInfo) {
    if info != nil {
        dc.DeskTopClientInfo = *info
    }
}

func (dc *DeskTopClient) onHeartBeat(info *DeskTopClientInfo) {
    dc.updateInfo(info)
    dc.HeartBeat = time.Now().Unix()
}

func (dc *DeskTopClient) idDead() bool {
    now := time.Now().Unix()
    config := getConfig()
    return now > dc.HeartBeat + config.Common.HBTimeout
}

type DeskTopClientMgr struct {
    clients map[string]*DeskTopClient
    lock sync.Mutex
}

var gDeskTopClientMgr *DeskTopClientMgr = nil
func DeskTopClientMgrInit() {
    gDeskTopClientMgr = &DeskTopClientMgr{}
    gDeskTopClientMgr.clients = make(map[string]*DeskTopClient)
    gDeskTopClientMgr.startCheckHearBeatLoop()
}

func (mgr *DeskTopClientMgr) onHeartBeat(info *DeskTopClientInfo) {
    mgr.lock.Lock()
    defer mgr.lock.Unlock()
    
    id := info.getId()
    dc, ok := mgr.clients[id]
    if !ok {
        dc = newDeskTopClient(info)
        mgr.clients[id] = dc
    }
    
    dc.onHeartBeat(info)
}

func (mgr *DeskTopClientMgr) onDisconnect(id string) {
    mgr.lock.Lock()
    defer mgr.lock.Unlock()
    
    _, ok := mgr.clients[id]
    if ok {
        mgr.clients[id] = nil
        delete(mgr.clients, id)
    }
}

func (mgr *DeskTopClientMgr) isOnline(id string) bool {
    mgr.lock.Lock()
    defer mgr.lock.Unlock()
    
    _, ok := mgr.clients[id]
    return ok
}

func (mgr *DeskTopClientMgr) getClientNum() int {
    mgr.lock.Lock()
    defer mgr.lock.Unlock()
    return len(mgr.clients)
}

func (mgr *DeskTopClientMgr) getClients() []*DeskTopClient {
    mgr.lock.Lock()
    defer mgr.lock.Unlock()
    list := make([]*DeskTopClient, 0)
    for _, c := range mgr.clients {
        list = append(list, c)
    }
    return list
}

func (mgr *DeskTopClientMgr) startCheckHearBeatLoop() {
    check := func() {
        list := mgr.getClients()
        
        for _, c := range list {
            if c.idDead() {
                mgr.onDisconnect(c.Id)
            }
        }
    }
    
    go func() {
        for {
            check()
            <- time.After(time.Second)
        }
    }()
}


type DesktopHttpApiData struct {
    DeskTopClientInfo
}

func (d *DesktopHttpApiData) getId() string {
    return d.DeskTopClientInfo.getId()
}

func getDesktopHttpApiData(w http.ResponseWriter, req *http.Request) (*DesktopHttpApiData, error) {
    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        errorResponse(500, false, HttpApiErrCommon, err.Error(), w)
        return nil, err
    }
    
    data := &DesktopHttpApiData{}
    err = json.Unmarshal(body, data)
    if err != nil {
        errorResponse(400, false, HttpApiErrBadRequest, err.Error(), w)
        return nil, err
    }
    
    if data.getId() == "" {
        err = errors.New("invalid node or vmid")
        errorResponse(400, false, HttpApiErrBadRequest, err.Error(), w)
        return nil, err
    }
    
    return data, nil
}

func handleDesktopClientNew(w http.ResponseWriter, req *http.Request) {
    data, err := getDesktopHttpApiData(w, req)
    if err != nil {
        return
    }
    
    config := getConfig()
    
    if gDeskTopClientMgr.isOnline(data.getId()) {
        errorResponse(500, false, HttpApiErrDesktopIsOnline, "is already online", w)
        return
    }
    
    if gDeskTopClientMgr.getClientNum() >= config.Common.MaxConn {
        errorResponse(500, false, HttpApiErrDesktopIsFull, "too many clients", w)
        return
    }
    
    gDeskTopClientMgr.onHeartBeat(&data.DeskTopClientInfo)
    
    errorResponse(200, true, 0, "", w)
}

func handleDesktopClientHeartBeat(w http.ResponseWriter, req *http.Request) {
    data, err := getDesktopHttpApiData(w, req)
    if err != nil {
        return
    }
    
    gDeskTopClientMgr.onHeartBeat(&data.DeskTopClientInfo)
    
    errorResponse(200, true, 0, "", w)
}

func handleDesktopClientDisconnect(w http.ResponseWriter, req *http.Request) {
    data, err := getDesktopHttpApiData(w, req)
    if err != nil {
        return
    }
    
    gDeskTopClientMgr.onDisconnect(data.getId())
    
    errorResponse(200, true, 0, "", w)
}

func handleDesktopClientState(w http.ResponseWriter, req *http.Request) {
    list := gDeskTopClientMgr.getClients()
    
    body, _ := json.Marshal(list)
    
    jsonResponse(200, body, w)
}